/***************************************************************************
 *   Copyright (C) 2008/2009 by Philipp Müller                             *
 *   philipp.mueller@gmx.de                                                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA          *
 ***************************************************************************/

#ifndef VDR_H
#define VDR_H

// #define NETWORK_DEBUG

#include <QMutex>
#include <QWaitCondition>
#include <QHostAddress>
#include <QTcpSocket>
#include <QDateTime>
#include <QTimer>
#include <QMetaType>

#include "ringbuffer.h"
#include "channellist.h"
#include "schedulelist.h"

struct RequestResponseEntry
{
    char*   data;
    quint32 mediaBlockMaxSize;
    quint64 fileSize;
    quint32 mediaWriten;
    bool    writen;
};


struct RecordingsEntry
{
    QDateTime start;
    QString   name;
    QString   fileName;
};


struct RecordingInfoComponent
{
    quint8  streams;
    quint8  types;
    QString languages;
    QString description;
};


struct RecordingInfo
{
    QDateTime timerStart;
    QDateTime timerEnd;
    quint32   resumePoint;
    QString   summary;
    qreal     fps;
    QList<RecordingInfoComponent> componentList;
};


struct TimerEntry
{
    bool operator< ( const TimerEntry &other ) const
    {
        return startTime < other.startTime;
    };
    bool operator== ( const TimerEntry &other ) const
    {
        return ( active        == other.active        ) &&
               ( recording     == other.recording     ) &&
               ( pending       == other.pending       ) &&
               ( priority      == other.priority      ) &&
               ( lifeTime      == other.lifeTime      ) &&
               ( channelNumber == other.channelNumber ) &&
               ( startTime     == other.startTime     ) &&
               ( stopTime      == other.stopTime      ) &&
               ( day           == other.day           ) &&
               ( weekDays      == other.weekDays      ) &&
               ( file == other.file );
    };
    bool      active;
    bool      recording;
    bool      pending;
    quint32   priority;
    quint32   lifeTime;
    quint32   channelNumber;
    QDateTime startTime;
    QDateTime stopTime;
    quint32   day;
    quint32   weekDays;
    QString   file;
};


struct MediaEntry
{
    quint32   type;
    QDateTime time;
    quint32   flags;
    QString   name;
};


struct ScheduleEntry
{
    quint32   id;
    QDateTime time;
    quint32   duration;
    QString   title;
    QString   subTitle;
    QString   description;
};


struct ChannelEntry
{
    quint32      channel;
    quint32      type;
    quint32      streamType;
    QString      name;
    ScheduleList scheduleList;
    QDateTime    scheduleListLastUpdate;
};


enum StreamType
{
    /* Types of the mediastreams */
    STREAM_RECORDING = 1,
    STREAM_MEDIA = 2,
    STREAM_TV = 100
};


/**
  Class to communicate with the vdr server

    @author Philipp Müller <philipp.mueller@gmx.de>
*/

class VDR : public QTcpSocket
{
        Q_OBJECT

    public:
        const static quint32 VIDEO = 1;
        const static quint32 RADIO = 2;

        VDR( QObject *parent = 0 );
        ~VDR();

        void setVDRServerAddress( QHostAddress vdr );
        qint32 requestLogin();


    signals:
        /** Signal emitted by main thread */
        void connectToVDRMainThread();

        /** write @array to the device. Called from outside */
        void toBeWriten( const QByteArray& array );

        /**
         * I have combined all functions/signals and slots to access the different VDR streams together in blocks,
         * as I think with this structure it is easier to understand how it works and what is needed for each
         */


        /**
         * LiveTV/Radio
         */

    public:
        quint32 streamChannel( quint32 channel );
        qint64 readDataFromBuffer( char * data, qint64 maxSize );

        /** Returns the bytes available in our ringBuffer for live-tv */
        qint64 streamBytesAvailable();

        /** Wait till some data have arrived in the live-tv stream */
        bool waitForStreamReadyRead( int msecs );

        /** Stops the stream. The function returns when the stream is stopped and the buffer is emptied */
        quint32 stopStreaming();

        /** Returns true if we are currently streaming. Needed for channel getting unavailable */
        bool isStreaming();

    signals:
        void channelListAvailable( const ChannelList & channelList );
        void streamChannelDone( quint32 result );
        void stopStreamingDone( quint32 result );
        void scheduleAvailable( quint32 channel, const ScheduleList & schedule );
        /**
            Emitted when a live-tv data package arrived and we inform iostream
            to also emit readyRead
        */
        void streamReadyRead();

    public slots:
        void getChannelList( quint32 channel );
        /** Request the EPG schedule data from vdr for the given channel. Starting point is now, duration is requested as 1 day */
        void getSchedule( quint32 channel );
        /** Request the EPG schedule data from vdr for the given channel. duration is in minutes */
        void getSchedule( quint32 channel, QDateTime dateTime, quint32 duration );

    private:
        void readChannelList( QDataStream& in, quint32 type, quint32 length );
        void readStream( QDataStream& in );
        void readStreamChannelDone( QDataStream& in );
        void readStopStreaming( QDataStream& in );
        void readSchedule( QDataStream& in, quint32 channel, quint32 length );
        QWaitCondition m_waitingForStreamStarted;
        QWaitCondition m_waitingForStreamReadyRead;
        QWaitCondition m_waitingForStreamStopped;
        quint32 m_streamResult;
        RingBuffer m_ringBuffer; //Buffer for the live-tv
        bool m_streaming;

        /**
         * Mediafiles
         */

    public:
        quint64 openMediaFile( const QString& file, quint32 x, quint32 y );
        qint64 readMedia( char * data, qint64 maxSize, quint64 position );

    signals:
        void mediaListAvailable( const QList<MediaEntry> & mediaList );

    public slots:
        void requestMediaList( const QString& dir );

    private:
        void readMediaList( QDataStream& in, quint32 length );
        void readMediaFileOpen( QDataStream& in );
        void readMediaBlock( quint32 packageID, quint32 length );

        QWaitCondition m_waitingForFileOpen;
        QWaitCondition m_waitingForMediaRead;
        quint64 m_fileSize;


        /**
         * Recordings
         */

    public:
        quint64 openRecording( const QString& file, quint32 &frames );
        qint64 readRecording( char * data, qint64 maxSize, quint64 position );
        RecordingInfo recordingInfo( const QString& file );
        quint64 positionFromFrameNumber( quint32 frame );
        quint32 frameNumberFromPosition( quint64 position );

    signals:
        void recordingsAvailable( const QList<RecordingsEntry> & recordingsList );

    public slots:
        void getRecordingsList();

    private:
        void readRecordingsList( QDataStream& in, quint32 length );
        void readRecordingOpen( QDataStream& in );
        void readRecordingInfo( QDataStream& in );
        void readRecordingBlock( quint32 packageID, quint32 length );
        void readFrameFromPosition( QDataStream& in );
        void readPositionFromFrame( QDataStream& in );

        QWaitCondition m_waitingForRecordingOpen;
        QWaitCondition m_waitingForRecordingRead;
        QWaitCondition m_waitingForRecordingInfo;
        QWaitCondition m_waitingForPositionRead;
        QWaitCondition m_waitingForFrameRead;

        quint64        m_recordingSize;
        quint32        m_recordingFrames; //Total number of frames
        quint8         m_isPES;           //Recording is PES
        quint64        m_recordingPosition;
        quint32        m_recordingFrame; //Requested frame
        RecordingInfo  m_recordingInfo;


        /**
         * Timers
         */

    signals:
        void timersAvailable( const QList<TimerEntry> & timerList );

        /** emitted after the timer has been set */
        void timerSet( int result );

        /** emitted after the timer has been deleted */
        void timerDeleted( int result );

        /** emitted after the timer has been changed */
        void timerChanged( int result );

    public slots:
        void getTimerList();
        /** Sets a new timer. If editTimer is true, it is part of the editTimer function */
        void setTimer( TimerEntry timer, bool editTimer = false );
        void deleteTimer( TimerEntry timer );
        /** Edits a timer. The function calls deleteTimer and setTimer on it's own */
        void editTimer( TimerEntry oldTimer, TimerEntry newTimer );

    private:
        void readTimerList( QDataStream& in, quint32 length );
        void readSetTimer( QDataStream& in );
        void readDeleteTimer( QDataStream& in );
        bool       m_editTimer;
        TimerEntry m_newTimer;


        /**
         * Configuration
         */
    public:
        QString loadConfig( const QString& section, const QString& key );
        bool saveConfig( const QString& section, const QString& key, const QString& value );

    private:
        void readConfigLoad( QDataStream& in );
        void readConfigSave( QDataStream& in );

        QWaitCondition m_waitingForConfigDone;
        quint64 m_ConfigSize;
        QString m_ConfigEntry;
        bool m_ConfigSave;


        /**
         * General functions
         */

    private slots:
        /** Connects to the host within thread */
        void connectToVDRWorkerThread();

        void writeToSocket( const QByteArray& array );
        void readFromSocket();
        void displayError( QAbstractSocket::SocketError socketError );
        void sendKeepAlive();
        void gotDisconnect();


    private:
        quint32 readMinPacketLength();
        quint32 requestPacketHeader( QDataStream & out, quint32 messageID, quint32 packageID = 0 );
        void readLogin( QDataStream& in );
        QWaitCondition m_waitingForLoginDone;

        bool readString( QDataStream& in, QString& string );
        bool writeString( QDataStream& in, const QString& string );

        void requestMediaBlock( quint64 position, qint32 maxAmount, quint32 packageID, quint32 cmd );

        QString getMacAddress();

        /** IP address of the server */
        QHostAddress m_VDRAddress;
        /** Difference of server time to local time in secs. Needed to synchronise channel schedule requests */
        qint32 m_serverTimeOffset;

        QTimer* m_keepAlive;

        QMutex m_mutex;

        quint32 m_requestBlock;
        QHash <quint32, QPair<quint32, quint32> > m_requestsHash;
        QHash <quint32, RequestResponseEntry> m_readHash;

        /* Static constants for communicating with server */
        const static ulong CHANNEL_REQUEST_RESPONSE = 1;
        const static ulong CHANNEL_STREAM = 2;
        const static ulong CHANNEL_KEEPALIVE = 3;

        const static ulong headerLength = 16;
        const static ulong userDataLenPos = 12;

        const static ulong VDR_LOGIN               = 1;
        const static ulong VDR_GETRECORDINGLIST    = 2;
        const static ulong VDR_DELETERECORDING     = 3;
        const static ulong VDR_GETCHANNELLIST      = 5;
        const static ulong VDR_STREAMCHANNEL       = 6;
        const static ulong VDR_GETBLOCK            = 7;
        const static ulong VDR_STOPSTREAMING       = 8;
        const static ulong VDR_STREAMRECORDING     = 9;
        const static ulong VDR_GETCHANNELSCHEDULE  = 10;
        const static ulong VDR_CONFIGSAVE          = 11;
        const static ulong VDR_CONFIGLOAD          = 12;
        const static ulong VDR_RESCANRECORDING     = 13;  // FIXME obselete
        const static ulong VDR_GETTIMERS           = 14;
        const static ulong VDR_SETTIMER            = 15;
        const static ulong VDR_POSFROMFRAME        = 16;
        const static ulong VDR_FRAMEFROMPOS        = 17;
        const static ulong VDR_MOVERECORDING       = 18;
        const static ulong VDR_GETNEXTIFRAME       = 19;
        const static ulong VDR_GETRECINFO          = 20;
        const static ulong VDR_GETMARKS            = 21;
        const static ulong VDR_GETCHANNELPIDS      = 22;
        const static ulong VDR_DELETETIMER         = 23;
        const static ulong VDR_GETMEDIALIST        = 30;
        const static ulong VDR_GETIMAGE            = 31;
        const static ulong VDR_GETIMAGEBLOCK       = 32;
        const static ulong VDR_GETLANGUAGELIST     = 33;
        const static ulong VDR_GETLANGUAGECONTENT  = 34;
};

#endif
